#ifndef XG_HTTPSERVER_CPP
#define XG_HTTPSERVER_CPP
////////////////////////////////////////////////////////
#include <thread>

#include "../HttpServer.h"
#include "../HttpStream.h"
#include "../HttpResponse.h"
#include "../../dbx/RedisConnect.h"
#include "../../openssl/SSLSocket.h"

#ifndef HTTP_GZIP_EXFMT
#define HTTP_GZIP_EXFMT		".gzip"
#endif

static HttpServer* app = NULL;

class SSLWorkItem : public WorkItem
{
public:
	int errcode;
	SOCKET sock;
	string addr;
	time_t utime;
	sp<SSLSocket> dest;

public:
	int init()
	{
		static SSLContext* context = SSLContext::GetGlobalContext();

		dest = newsp<SSLSocket>();
		dest->setCloseFlag(false);

		if (dest->init(sock, context))
		{
			utime = time(NULL);
			
			return XG_OK;
		}

		return XG_SYSERR;
	}
	void run()
	{
		if (errcode == XG_OK || errcode == XG_TIMEOUT)
		{
			if (ServerSocketAttach(sock, addr.c_str()))
			{
				utime = time(NULL);
			}
			else
			{
				HttpServer::ReleaseConnect(sock, addr.c_str());
			}
		}
		else
		{
			HttpServer::ReleaseConnect(sock, addr.c_str());
		}
	}
	int accept()
	{
		int res = dest->accept();

		if (res > 0)
		{
			LogTrace(eDBG, "connect[%s] success", addr.c_str());
		}

		return res;
	}
	bool runnable()
	{
		errcode = accept();

		if (errcode < 0)
		{
			if (utime + 3 < time(NULL)) errcode = XG_NETERR;
		}
		else
		{
			errcode = XG_OK;
		}

		return true;
	}
	SSLWorkItem(SOCKET sock, const string& addr)
	{
		this->errcode = XG_ERROR;
		this->utime = time(NULL);
		this->addr = addr;
		this->sock = sock;
	}
};

class LoadCgiMapItem : public WorkItem
{
private:
	string key;

public:
	void run()
	{
		app->getCgiMapData(key);
	}
	LoadCgiMapItem(const string& key)
	{
		this->key = key;
	}
};

int CheckHost::check(int host, int maxcnt)
{
	time_t now = time(NULL);
	time_t etime = now - 60;

	if (hostmap.size() > 100000)
	{
		auto it = hostmap.begin();

		while (it != hostmap.end())
		{
			if (it->second.ctime < etime)
			{
				hostmap.erase(it++);
			}
			else
			{
				++it;
			}
		}
	}

	Counter& cnt = hostmap[host];

	if (cnt.ctime < etime)
	{
		cnt.ctime = now;
		cnt.num = 1;

		return 0;
	}

	if (++cnt.num <= maxcnt) return 0;

	cnt.ctime = now + 300 + rand() % 300 + stdx::minval(cnt.num, 300);

	return cnt.ctime - now;
}
	
int HttpServer::getPort()
{
	if (memsz < 0) getShareData();

	return port;
}
string HttpServer::getHost()
{
	if (memsz < 0) getShareData();

	return host == HOST_IP ? LOCAL_IP : host.c_str();
}
void HttpServer::removeCgiAccess(const string& url)
{
	accessmap.remove(CgiMapData::GetKey(url));
}
int HttpServer::getCgiAccess(const string& url) const
{
	int access = CGI_PRIVATE;
	string key = CgiMapData::GetKey(url);

	if (accessmap.get(key, access)) return access;

	defaccessmap.get(key, access);

	return access;
}
void HttpServer::setCgiAccess(const string& url, int access)
{
	string key = CgiMapData::GetKey(url);

	defaccessmap.set(key, access);

	updateCgiAccess(key, access);
}
void HttpServer::updateCgiAccess(const string& key, int access)
{
	accessmap.set(key, access);

	cgimap.update(key, [&](CgiMapData& item){
		item.access = access;
	});
}
string HttpServer::getCgiExtdata(const string& url) const
{
	string extdata;

	cgiextmap.get(CgiMapData::GetKey(url), extdata);

	return extdata;
}
void HttpServer::setCgiExtdata(const string& url, const string& extdata)
{
	string key = CgiMapData::GetKey(url);

	cgiextmap.set(key, extdata);

	cgimap.update(key, [&](CgiMapData& item){
		item.extdata = extdata;
	});
}
map<string, tuple<string, string, string>> HttpServer::getCgiDocMap() const
{
	map<string, tuple<string, string, string>> docmap;

	cgidocmap.get(docmap);

	return std::move(docmap);
}
tuple<string, string, string> HttpServer::getCgiDoc(const string& url) const
{
	tuple<string, string, string> doc;

	cgidocmap.get(CgiMapData::GetKey(url), doc);

	return doc;
}
void HttpServer::setCgiDoc(const string& url, const string& reqdoc, const string& rspdoc, const string& remark)
{
	string key = CgiMapData::GetKey(url);

	cgidocmap.set(key, make_tuple(reqdoc, rspdoc, remark));
}
void HttpServer::setupLogCallback()
{
	static int idx;

	idx = 0;

	LogThread::Instance()->callback([&](const string& msg){
		auto sendLog = [&](const string& msg){
			if (logcallback) logcallback(msg.c_str(), msg.length());
		};

		sendLog(msg);

		if (sem.wait())
		{
			int len = mq.push(msg.c_str(), msg.length());

			sem.release();

			if (len < 0 && msg.length() < 0xFF)
			{
				Sleep(10);

				if (sem.wait())
				{
					len = mq.push(msg.c_str(), msg.length());

					sem.release();
				}

				if (len < 0 && ++idx > 100) LogThread::Instance()->callback(sendLog);
			}
		}
	});
}
bool HttpServer::loadSystem(bool updated)
{
	if (SkipLoadWebApp()) return true;

	if (updated) CHECK_FALSE_RETURN(yaml::reload());

	yaml::list("app.alloworigin", alloworiginlist);
	yaml::config("app.switch.route", routeswitch);
	yaml::config("app.connect.timeout", timeout);
	reqmaxsz = yaml::capacity("app.connect.maxrequestsize");
	requestimes = yaml::capacity("app.connect.maxrequestcount");

	for (string& host : alloworiginlist)
	{
		stdx::tolower(host);

		if (stdx::startwith(host, "https://"))
		{
			host = host.substr(8);
		}
		else if (stdx::startwith(host, "http://"))
		{
			host = host.substr(7);
		}
	}

	if (requestimes <= 0) requestimes = HTTP_REQUEST_MAXTIMES;
	if (reqmaxsz <= 0) reqmaxsz = HTTP_REQDATA_MAXSIZE;
	if (timeout <= 0) timeout = 60;

	idxpathset.clear();
	cachefile.clear();
	pathmap.clear();
	cfgmap.clear();

	if (cgimap.size() > 0)
	{
		cgimap.clear();

		sleep(1);
	}

	int level = 0;
	int threads = 0;

	yaml::config("log.level", level);

	if (level >= 0) LogThread::Instance()->setLevel(level);

	if (yaml::config("app.threads", threads))
	{
		if (threads < 1) threads = 1;
	}
	else
	{
		threads = GetCpuCount() << 1;

		if (threads < 8) threads = 8;
	}

	if (threads != TaskQueue::Instance()->getThreads()) TaskQueue::Instance()->start(threads);

	HttpSettingItem::GetCommonFrame(true);

	auto loadWebapp = [&](string path){
		vector<string> vec;

		CHECK_FALSE_RETURN(stdx::FindFile(vec, path, "*.so") > 0);

		if (!stdx::endwith(path, "/")) path += "/";

		std::sort(vec.begin(), vec.end(), [](const string& a, const string& b){
			size_t m = a.find("/plugin/");
			size_t n = b.find("/plugin/");
			
			if (m == string::npos && n == string::npos) return a < b;

			if (m == string::npos) return false;

			if (n == string::npos) return true;

			return a < b;
		});

		for (auto& item : vec)
		{
			string url = item.substr(path.length());

			if (stdx::startwith(url, ".")) continue;
			if (stdx::startwith(url, "dat/")) continue;

			if (addCgiData(url, item))
			{
				if (url.length() > 0)
				{
					LogTrace(eINF, "auto load[%s][%s] success", url.c_str(), item.c_str());
				}
			}
			else
			{
				LogTrace(eERR, "auto load[%s][%s] failed", url.c_str(), item.c_str());
			}
		}

		return true;
	};

	string installpath = proc::env("CPPWEB_INSTALL_HOME");
	
	CHECK_FALSE_RETURN(installpath.length() > 0);

	if (webapp)
	{
		loadWebapp(installpath + "/webapp/");
		loadWebapp(path);
	}

	mimemap = {
		{"js", "text/javascipt"},
		{"cache", "text/cache-manifest"},
		{"appcache", "text/cache-manifest"},
		{"xml", "text/xml"},
		{"css", "text/css"},
		{"png", "image/png"},
		{"bmp", "image/bmp"},
		{"gif", "image/gif"},
		{"htm", "text/html"},
		{"html", "text/html"},
		{"jpg", "image/jpeg"},
		{"jpeg", "image/jpeg"},
		{"ico", "image/x-icon"},
		{"pdf", "application/pdf"},
		{"doc", "application/msword"},
		{"xls", "application/vnd.ms-excel"},
		{"ppt", "application/vnd.ms-powerpoint"},
		{"json", "application/json"},
		{"docx", "application/msword"},
		{"xlsx", "application/vnd.ms-excel"},
		{"pptx", "application/vnd.ms-powerpoint"},
		{"tar", "application/x-compressed"},
		{"tgz", "application/x-compressed"},
		{"cab", "application/x-compressed"},
		{"rar", "application/x-compressed"},
		{"zip", "application/x-zip-compressed"},
		{"chm", "application/octet-stream"},
		{"dll", "application/octet-stream"},
		{"exe", "application/octet-stream"},
		{"msi", "application/octet-stream"},
		{"ocx", "application/octet-stream"}
	};

	auto loadCgiMap = [&](const string& tag, function<void(string, string)> func){
		auto itemap = yaml::list("app." + tag);

		if (webapp)
		{
			if (tag == "dir")
			{
				auto add = [&](const string& name){
					itemap[name] = installpath + "/webapp/" + name;
				};

				add("app/confile/css");
				add("app/confile/pub");
				add("app/compile/css");
				add("app/compile/pub");
				add("app/tableview/css");
				add("app/tableview/pub");
				add("app/workspace/css");
				add("app/workspace/pub");
			}
		}

		for (auto& item : itemap)
		{
			string key = CgiMapData::GetKey(item.first);

			cfgmap[tag + "@" + key] = item.second;

			if (func) func(key, item.second);
		}
	};

	loadCgiMap("url", [&](string key, string val){
	});

	loadCgiMap("idx", [&](string key, string val){
		idxpathset.insert(key);
		pathmap[key] = val;
	});

	loadCgiMap("dir", [&](string key, string val){
		pathmap[key] = val;
	});

	loadCgiMap("cgi", [&](string key, string val){
		stdx::async(newsp<LoadCgiMapItem>(key));
	});

	loadCgiMap("mime", [&](string key, string val){
		mimemap[key] = val;
	});

	return true;
}
bool HttpServer::loadConfig(const string& filepath)
{
	CHECK_FALSE_RETURN(yaml::open(filepath));

	yaml::config("app.id", id);
	yaml::config("app.name", name);
	yaml::config("app.path", path);
	yaml::config("app.port", port);
	yaml::config("app.host", host);

	if (id < 1) id = 1;
	if (host.empty()) host = HOST_IP;
	if (name.empty()) name = Process::GetAppname();
	if (path.empty() || path == "%WEBAPP_ROOT_PATH%") path = Process::GetCurrentDirectory();

	if (name.length() >= MAX_PATH) name = name.substr(0, MAX_PATH - 1);
	if (path.length() >= MAX_PATH) path = path.substr(0, MAX_PATH - 1);

	path += "/";
	path = stdx::replace(path, "\\", "/");
	path = stdx::replace(path, "//", "/");

	CHECK_FALSE_RETURN(Process::SetEnv("WEBAPP_ROOT_PATH", path));
	CHECK_FALSE_RETURN(Process::SetCurrentDirectory(path));

	XFile sequencefile;
	string sequencepath = path + ".sequence";

	if (stdx::GetFileContent(sequencehead, sequencepath))
	{
		sequencehead = stdx::trim(sequencehead);
	}

	int len = stdx::maxval(sequencehead.length(), 2);
	int val = stdx::maxval(stdx::atoi(sequencehead.c_str()), 0);

	sequencehead = stdx::str(++val);

	if (sequencehead.length() > len) sequencehead.clear();

	sequencehead = stdx::fill(sequencehead, len, true, '0');

	path::remove(sequencepath);

	CHECK_FALSE_RETURN(sequencefile.create(sequencepath));
	CHECK_FALSE_RETURN(sequencefile.write(sequencehead.c_str(), len) > 0);

	SetPathAttributes(sequencepath.c_str(), eHIDDEN);

	sequencehead = stdx::format("%03d", id % 1000) + sequencehead;

	return true;
}
void HttpServer::close()
{
	TaskQueue::Instance()->stop();
	LogThread::Instance()->wait();
	cgimap.clear();
}
bool HttpServer::initSystem(bool destroyed)
{
	static DllFile dll;
	static HttpProcessBase* cgi = NULL;
	static CREATE_HTTP_CGI_FUNC crtfunc = NULL;
	static DESTROY_HTTP_CGI_FUNC dsyfunc = NULL;

	if (destroyed)
	{
		if (cgi)
		{
			dsyfunc(cgi);
			cgi = NULL;
		}
		
		return true;
	}

	if (path.empty()) path = proc::pwd() + "/";

	if (crtfunc == NULL || dsyfunc == NULL)
	{
		string dllpath = stdx::translate("$CPPWEB_INSTALL_HOME/webapp/etc/plugin/bin/InitSystem.so");

		CHECK_FALSE_RETURN(path::type(dllpath) == eFILE && dll.open(dllpath));

		CHECK_FALSE_RETURN(dll.read(crtfunc, "CreateHttpProcessObject") && dll.read(dsyfunc, "DestroyHttpProcessObject"));
	}

	if (cgi) dsyfunc(cgi);

	CHECK_FALSE_RETURN(cgi = crtfunc());

	cgidata.flag = CgiMapData::CGI_FLAG;
	cgidata.destroy_cgi = dsyfunc;
	cgidata.create_cgi = crtfunc;

	return cgi->doWork(NULL, NULL) >= 0;
}

static int ProcessRequestNoException(SOCKET conn, stConnectData* data)
{
	CATCH_EXCEPTION({
		return HttpServer::ProcessRequest(conn, data);
	});

	return XG_SYSERR;
}

static int ProcessConnectClosedNoException(SOCKET conn, stConnectData* data)
{
	CATCH_EXCEPTION({
		return HttpServer::ProcessConnectClosed(conn, data);
	});

	return XG_SYSERR;
}

static int ProcessConnectNoException(SOCKET conn, stConnectData* data, const char* host, int port)
{
	CATCH_EXCEPTION({
		return HttpServer::ProcessConnect(conn, data, host, port);
	});

	return XG_SYSERR;
}

bool HttpServer::init(const string& filepath, bool webapp)
{
	close();
	app = Instance();
	this->webapp = webapp;

	ServerSocketSetLockFunction(Lock, Unlock);
	ServerSocketSetConnectFunction(ProcessConnectNoException);
	ServerSocketSetProcessFunction(ProcessRequestNoException);
	ServerSocketSetConnectClosedFunction(ProcessConnectClosedNoException);

	string prodpath = proc::env("CPPWEB_PRODUCT_HOME");

	if (prodpath.empty())
	{
		string installpath = proc::env("CPPWEB_INSTALL_HOME");

		CHECK_FALSE_RETURN(installpath.length() > 0);

#ifdef XG_LINUX
		Process::SetEnv("CPPWEB_PRODUCT_HOME", installpath + "/product");
#else
		Process::SetEnv("CPPWEB_PRODUCT_HOME", installpath + "/product/win");
#endif		
	}

	CHECK_FALSE_RETURN(loadConfig(filepath));
	CHECK_FALSE_RETURN(LogThread::Instance()->init(YAMLoader::Instance()));

	if (port == 0)
	{
		srand(time(NULL) + clock());

		string ip = host == HOST_IP ? LOCAL_IP : host;

		for (int i = 0; i < 10; i++)
		{
			if (port == 0)
			{
				string msg = stdx::format("%d:%s:%s", id, name.c_str(), stdx::GetProcessExePath());

				port = 10000 + MD5GetUINT32(msg.c_str(), msg.length()) % 30000;
			}
			else
			{
				port = stdx::random(10000, 30000);
			}

			if (Socket().connect(ip.c_str(), port, 100))
			{
				LogTrace(eIMP, "random port[%d] check failed", port);
			}
			else
			{
				LogTrace(eINF, "random port[%d] check success", port);

				break;
			}
		}
	}

	CHECK_FALSE_RETURN(port > 0 && host.length() > 0);

	yaml::config("app.ssl.port", sslport);
	yaml::config("app.ssl.host", sslhost);

	if (sslport > 0)
	{
		if (sslhost.empty()) sslhost = host;

		string keyfile;
		string certfile;
		string cipherlist;

		yaml::config("app.ssl.certfile", certfile);
		yaml::config("app.ssl.prikeyfile", keyfile);
		yaml::config("app.ssl.cipherlist", cipherlist);

		SSLContext* context = SSLContext::GetGlobalContext();

		if (context->setCertificate(certfile))
		{
			LogTrace(eIMP, "load certificate[%s] success", certfile.c_str());
		}
		else
		{
			LogTrace(eERR, "load certificate[%s] failed", certfile.c_str());

			sslport = 0;
		}

		if (context->setCertPrivateKey(keyfile))
		{
			LogTrace(eIMP, "load privatekey[%s] sussess", keyfile.c_str());
		}
		else
		{
			LogTrace(eERR, "load privatekey[%s] failed", keyfile.c_str());

			sslport = 0;
		}

		if (sslport <= 0)
		{
			LogTrace(eIMP, "skip https initialize");
		}
		else
		{		
			context->setClientVerify(false);

			if (cipherlist.length() > 0) context->setCipherList(cipherlist);

			SSL_CTX_load_verify_locations(context->getHandle(), certfile.c_str(), NULL);
			SSL_CTX_set_options(context->getHandle(), SSL_OP_CIPHER_SERVER_PREFERENCE);

#if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_1_0_2
			SSL_CTX_set_alpn_select_cb(context->getHandle(), SSLContext::SelectALPN, NULL);
			SSL_CTX_set_options(context->getHandle(), SSL_OP_SINGLE_ECDH_USE);
			SSL_CTX_set_ecdh_auto(context->getHandle(), 1);
#endif
		}
	}

	const int memsz = HTTP_SERVER_IPC_SHMSIZE * (sizeof(ShareData) / HTTP_SERVER_IPC_SHMSIZE + 1);

	CHECK_FALSE_RETURN(shm.open(GetSharememName()) || shm.create(GetSharememName(), memsz));
	CHECK_FALSE_RETURN(sem.open(GetSemaphoreName()) || sem.create(GetSemaphoreName()));

	if (webapp)
	{
		initSystem(true);

		if (initDatabase())
		{
			LogTrace(eIMP, "initialize database success");
		}
		else
		{
			LogTrace(eERR, "initialize database failed");
		}
	}

	loadSystem(false);

	if (webapp)
	{
		if (initSystem(false))
		{
			LogTrace(eIMP, "initialize system success");
		}
		else
		{
			LogTrace(eIMP, "skip system initialize");
		}
	}

	updateCgiData(stdx::EmptyString());

	ShareData* shd = getShareData();

	CHECK_FALSE_RETURN(shd);

	sem.release();

	if (sem.wait())
	{
		mq.create(shd->logdata, sizeof(shd->logdata));

		sem.release();

		setupLogCallback();
	}

	memset(shd->data, 0, sizeof(shd->data));
	strcpy(shd->name, name.c_str());
	strcpy(shd->path, path.c_str());
	strcpy(shd->host, host.c_str());
	shd->port = port;

	return true;
}
HttpServer::ShareData* HttpServer::getShareData()
{
	ShareData* shd = NULL;

	if (shm.canUse()) return (ShareData*)(shm.get());

	if (shm.open(GetSharememName()))
	{
		if ((memsz = shm.size()) < 0) return shd;

		shd = (ShareData*)(shm.get());

		if (id == 0)
		{
			name = shd->name;
			path = shd->path;
			host = shd->host;
			port = shd->port;
		}
	}

	return shd;
}
bool HttpServer::initDatabase()
{
	if (dbconnpool) return true;

	auto cfg = YAMLoader::Instance();
	string name = yaml::config("database.name");

	if (name.empty())
	{
		string parent = path::parent(cfg->getFilePath());

		if (parent.empty())
		{
			name = "sqlite.db";
		}
		else
		{
			name = parent + "/sqlite.db";
		}

		cfg->set("database.name", name);
		cfg->set("database.maxsize", 8);
		cfg->set("database.timeout", 60);

		XFile file;

		file.open(name, eWRITE);

		if (file.size() <= 0 && file.open(name, eCREATE))
		{
			SmartBuffer GetSQLiteTemplate();
			SmartBuffer data = GetSQLiteTemplate();

			file.write(data.str(), data.size());
			file.flush();
			file.close();
		}
	}

	if (name.length() > 0)
	{
		CHECK_FALSE_RETURN(dbconnpool = DBConnectPool::Create(cfg));
	}

	int port = yaml::config<int>("redis.port");
	string host = yaml::config<string>("redis.host");
	string password = yaml::config<string>("redis.password");

	if (port > 0 && host.length() > 0)
	{
		Socket sock;
		
		if (sock.connect(host, port))
		{
			RedisConnect::Setup(host, port, password);

			sp<RedisConnect> redis = RedisConnect::Instance();

			if (redis)
			{
				LogTrace(eIMP, "initialize redis success");
			}
			else
			{
				LogTrace(eERR, "initialize redis failed");
			}
		}
		else
		{
			LogTrace(eERR, "connect redis failed");
		}
	}

	return true;
}
bool HttpServer::updateModuleFile(const string& src, const string& dest)
{
	DllFile dll;
	GET_HTTP_CGI_PATH_FUNC GetHttpCgiPath = NULL;
	GET_HTTP_CGI_PATH_FUNC GetHttpCgiPathList = NULL;
	string backup = dest + "." + DateTime::GetBizId().substr(0, 14);

	CHECK_FALSE_RETURN(dll.open(src));

	dll.read(GetHttpCgiPath, "GetHttpCgiPath");
	dll.read(GetHttpCgiPathList, "GetHttpCgiPathList");

	CHECK_FALSE_RETURN(GetHttpCgiPath || GetHttpCgiPathList);

	dll.close();

	Sleep(100);

	path::rename(dest, backup);

	return path::rename(src, dest);
}

sp<HttpResponse> HttpServer::getLocaleResult(const HttpRequest& request, int timeout)
{
	return request.getResponse(getHost(), getPort(), false, true, timeout);
}
int HttpServer::command(char* cmd)
{
	char ch = *cmd;

	*cmd = 0;

	if (ch == 'k')
	{
		LogTrace(eIMP, "stop process success");

		Process::SetDaemonCommand("exit");

		timeout = 0;
		cmd[1] = 0;
		close();

		ErrorExit(0);
	}

	if (ch == 's')
	{
		if (Process::SetDaemonCommand("restart"))
		{
			LogTrace(eIMP, "restart process success");

			timeout = 0;
			cmd[1] = 0;
			close();

			ErrorExit(0);
		}

		LogTrace(eERR, "restart process failed");

		cmd[1] = 1;
	}

	if (ch == 'r')
	{
		LogThread::Instance()->wait();

		if (loadSystem(true))
		{
			LogTrace(eINF, "reload configure success");

			cmd[1] = 0;
		}
		else
		{
			LogTrace(eERR, "reload configure failed");

			cmd[1] = 1;
		}

		if (cgidata.create_cgi)
		{
			HttpProcessBase* cgi = cgidata.create_cgi();

			if (cgi)
			{
				cgi->doWork(NULL, NULL);
				cgidata.destroy_cgi(cgi);
			}
		}

		updateCgiData(stdx::EmptyString());

		hostmtx.lock();
		hostmap.clear();
		hostmtx.unlock();
	}

	if (ch == 'l')
	{
		cmd[1] = 1;

		if (sem.wait())
		{
			mq.clear();

			sem.release();
		}

		setupLogCallback();
	}

	return XG_OK;
}
bool HttpServer::updateCgiData(const string& url)
{
	int res = 0;
	int access = 0;
	sp<RowData> row;
	sp<QueryResult> rs;
	string key = CgiMapData::GetKey(url);
	sp<DBConnect> dbconn = getDBConnect();

	CHECK_FALSE_RETURN(dbconn);

	if (key.empty())
	{
		string sqlcmd = "SELECT PATH,MAXSZ,MAXCNT,ENABLED,HOSTMAXCNT FROM T_XG_CGI";

		CHECK_FALSE_RETURN(rs = dbconn->query(sqlcmd));
	}
	else
	{
		string sqlcmd = "SELECT PATH,MAXSZ,MAXCNT,ENABLED,HOSTMAXCNT FROM T_XG_CGI WHERE PATH=?";

		CHECK_FALSE_RETURN(rs = dbconn->query(sqlcmd, key));
	}

	while (row = rs->next())
	{
		string key = row->getString(0);

		cgimap.update(key, [&](CgiMapData& item){
			item.maxsz = row->getInt(1);
			item.maxcnt = row->getInt(2);
			item.access = row->getInt(3);
			item.hostmaxcnt = row->getInt(4);

			access = item.access;
		});

		updateCgiAccess(key, access);

		++res;
	}

	if (res == 0 && key.length() > 0)
	{
		cgimap.update(key, [&](CgiMapData& item){
			item.maxcnt = 0;
			item.hostmaxcnt = 0;
			item.access = getCgiAccess(key);
			item.extdata = getCgiExtdata(key);
			item.maxsz = HTTP_REQDATA_MAXSIZE;
		});
	}

	return true;
}
CgiMapData HttpServer::getCgiMapData(const string& url)
{
	string link = stdx::trim(url, HTTP_SPACELIST);
	string key = link.empty() ? "index" : link;
	CgiMapData item;

	if (cgimap.get(stdx::tolower(key), item)) return item;

	string val;
	SpinLocker lk(mtx);
	auto it = cfgmap.find("url@" + key);

	if (it != cfgmap.end())
	{
		item.flag = CgiMapData::URL_FLAG;
		val = it->second;
	}
	else
	{
		it = cfgmap.find("cgi@" + key);
	
		if (it != cfgmap.end())
		{
			item.flag = CgiMapData::CGI_FLAG;
			val = it->second;
		}
	}
	
	if (val.empty())
	{
		auto it = pathmap.find(key);

		if (it != pathmap.end())
		{
			link = it->second;
		}
		else
		{
			string parent = path::parent(link);

			while (parent.length() > 0)
			{
				it = pathmap.find(parent);

				if (it != pathmap.end())
				{
					link = it->second + link.substr(parent.length());

					break;
				}

				parent = path::parent(parent);
			}

			if (parent.empty())
			{
				it = pathmap.find("index");

				if (it != pathmap.end())
				{
					link = it->second + "/" + link;
				}
				else
				{
					if (url.length() < 5) return CgiMapData::NullObject();
		
					if (url.find("../") != string::npos) return CgiMapData::NullObject();
		
					if (url.find("/pub/") == string::npos && url.find("/css/") == string::npos && memcmp(url.c_str(), "pub/", 4) && memcmp(url.c_str(), "css/", 4)) return CgiMapData::NullObject();
				}
			}
		}

		char ch = link[0];

		item.flag = CgiMapData::URL_FLAG;

		if (ch == '@')
		{
			item.url = link.substr(1);
		}
		else if (ch == '/' || ch == '~' || ch == '\\' || link[1] == ':')
		{
			item.url = link;
		}
		else
		{
			item.url = path + link;
		}

		int type = path::type(item.url);

		if (type < ePATH) return CgiMapData::NullObject();

		if (type == ePATH)
		{
			auto checkIndexPath = [&](const string& path){
				if (idxpathset.find(path) != idxpathset.end()) return true;

				if (idxpathset.find("index") != idxpathset.end()) return true;

				size_t pos = path.length();

				while (--pos > 0)
				{
					if (path[pos] == '/')
					{
						if (idxpathset.find(path.substr(0, pos)) != idxpathset.end()) return true;
					}
				}

				return false;
			};

			if (checkIndexPath(key))
			{
				item.flag = CgiMapData::IDX_FLAG;
			}
			else
			{
				return CgiMapData::NullObject();
			}
		}
	}
	else
	{
		size_t pos = val.find('?');

		if (pos != string::npos)
		{
			item.param = val.substr(pos + 1);
			val = val.substr(0, pos);
		}
	
		char ch = val[0];

		item.maxsz = reqmaxsz;

		if (ch == '@')
		{
			item.url = val.substr(1);
		}
		else if (ch == '/' || ch == '~' || ch == '\\' || val[1] == ':')
		{
			item.url = val;
		}
		else
		{
			item.url = path + val;
		}
	}

	if (item.flag == CgiMapData::URL_FLAG)
	{
		const string& filepath = item.url;
		static const size_t GZIP_EXFMT_LEN = strlen(HTTP_GZIP_EXFMT);
	
		if (filepath.length() > GZIP_EXFMT_LEN)
		{
			string key = filepath.c_str() + filepath.length() - GZIP_EXFMT_LEN;
	
			if (stdx::tolower(key) == HTTP_GZIP_EXFMT) item.code = CgiMapData::GZIP_CODE;
		}
	}

	CgiMapData tmp = item;
	string dest = tmp.url;

	if (item.url.find(path) == 0)
	{
		dest = item.url.substr(path.length());
	}

	dest = CgiMapData::GetKey(dest);

	if (cgimap.get(dest, item))
	{
		item.dest = dest;

		if (item.param.empty())
		{
			item.param = tmp.param;
		}
		else
		{
			item.param += "&" + tmp.param;
		}
	}
	else
	{
		string path = key;

		stdx::tolower(path);

		if (IsDynamicLoad())
		{
			LogTrace(eINF, "dynamic load[%s][%s] success", path.c_str(), item.url.c_str());
		}
		else
		{
			if (item.flag == CgiMapData::CGI_FLAG)
			{
				sp<DllFile> dll = newsp<DllFile>();

				if (dll->open(item.url))
				{
					dll->read(item.create_cgi, "CreateHttpProcessObject");
					dll->read(item.destroy_cgi, "DestroyHttpProcessObject");
				}

				if (item.create_cgi == NULL || item.destroy_cgi == NULL) return CgiMapData::NullObject();

				item.dll = dll;
			}

			LogTrace(eINF, "static load[%s][%s] success", path.c_str(), item.url.c_str());
		}
	}

	if (cgimap.size() > 1000000) cgimap.clear();

	cgimap.set(key, item);

	updateCgiData(key);

	cgimap.get(key, item);

	return item;
}
int HttpServer::getCgiMap(map<string, CgiMapData>& cgimap)
{
	return this->cgimap.get(cgimap);
}
bool HttpServer::addCgiData(string& url, const string& path)
{
	if (path.empty())
	{
		CgiMapData item(CgiMapData::CGI_FLAG, CgiMapData::NONE_CODE, url);

		item.access = getCgiAccess(url);
		item.extdata = getCgiExtdata(url);
		item.create_cgi = cgidata.create_cgi;
		item.destroy_cgi = cgidata.destroy_cgi;
		cgimap.set(CgiMapData::GetKey(url), item);

		return true;
	}

	sp<DllFile> dll;
	vector<string> vec;
	string extname = path::extname(path);
	CgiMapData item(CgiMapData::URL_FLAG, CgiMapData::NONE_CODE, path);

	auto getPath = [&](const char* cgipath){
		if (cgipath == NULL || *cgipath == 0) return XG_OK;

		string key = stdx::trim(cgipath);

		if (key.empty()) return XG_OK;

		if (key == "@null" || key == "@hidden" || key == "@disable")
		{
			url.clear();

			return XG_NOTFOUND;
		}

		url = GetWebAppPath(key.c_str(), path.c_str());

		return XG_OK;
	};
	
	if (IsDynamicLoad())
	{
		dll = newsp<DllFile>();

		if (dll->open(item.url)) item.flag = CgiMapData::CGI_FLAG;
	}
	else
	{
		dll = newsp<DllFile>();

		if (dll->open(item.url))
		{
			dll->read(item.create_cgi, "CreateHttpProcessObject");
			dll->read(item.destroy_cgi, "DestroyHttpProcessObject");

			if (item.create_cgi == NULL || item.destroy_cgi == NULL) return false;

			item.flag = CgiMapData::CGI_FLAG;
			item.dll = dll;
		}
	}

	if (item.flag == CgiMapData::CGI_FLAG)
	{
		GET_HTTP_CGI_PATH_FUNC GetHttpCgiPathList = NULL;

		if (dll->read(GetHttpCgiPathList, "GetHttpCgiPathList"))
		{
			const char* path = GetHttpCgiPathList();

			if (path) stdx::split(vec, stdx::trim(path), "|");
		}

		if (vec.empty())
		{
			GET_HTTP_CGI_PATH_FUNC GetHttpCgiPath = NULL;

			if (dll->read(GetHttpCgiPath, "GetHttpCgiPath"))
			{
				if (getPath(GetHttpCgiPath()) == XG_NOTFOUND) return true;
			}
		}
	}

	if (item.flag == CgiMapData::URL_FLAG)
	{
		const string& filepath = item.url;
		static const size_t GZIP_EXFMT_LEN = strlen(HTTP_GZIP_EXFMT);

		if (filepath.length() > GZIP_EXFMT_LEN)
		{
			string key = filepath.c_str() + filepath.length() - GZIP_EXFMT_LEN;

			if (stdx::tolower(key) == HTTP_GZIP_EXFMT) item.code = CgiMapData::GZIP_CODE;
		}
	}

	if (vec.empty())
	{
		item.access = getCgiAccess(url);
		item.extdata = getCgiExtdata(url);
		cgimap.set(CgiMapData::GetKey(url), item);
	}
	else
	{
		for (string& url : vec)
		{
			url = CgiMapData::GetKey(url);

			if (url.empty()) continue;

			item.access = getCgiAccess(url);
			item.extdata = getCgiExtdata(url);
			cgimap.set(CgiMapData::GetKey(url), item);
			
			LogTrace(eINF, "auto load[%s][%s] success", url.c_str(), path.c_str());
		}

		url.clear();
	}

	return true;
}
string HttpServer::getName()
{
	if (memsz < 0) getShareData();
	
	return name;
}
string HttpServer::getPath()
{
	if (memsz < 0) getShareData();
	
	return path;
}
string HttpServer::getSequence()
{
	char buffer[16];
	static atomic_int idx(0);

	sprintf(buffer, "%09d", ++idx % 1000000000);

	return sequencehead + buffer;
}
void HttpServer::removeSession(const string& name)
{
	sessmap.remove(name);
}
sp<Session> HttpServer::getSession(const string& name, int timeout)
{
	sp<HttpSession> session;

	if (sessmap.get(name, session))
	{
		if (!session->isTimeout()) return session;

		sessmap.remove(name);
		session = NULL;
	}

	if (timeout <= 0) return session;

	sessmap.set(name, session = sp<HttpSession>(new HttpSession(name, timeout)));

	LogTrace(eINF, "create session[%s][%d]", name.c_str(), timeout);

	return session;
}

sp<DBConnect> HttpServer::getDBConnect()
{
	return dbconnpool ? dbconnpool->get() : NULL;
}
void HttpServer::disableDBConnect(sp<DBConnect> conn)
{
	if (dbconnpool) dbconnpool->disable(conn);
}
string HttpServer::getMimeType(const string& key) const
{
	auto it = mimemap.find(key);
		
	return it == mimemap.end() ? stdx::EmptyString() : it->second;
}
int HttpServer::getFileContent(const string& path, SmartBuffer& content)
{
	return cachefile.getContent(path, content);
}
int HttpServer::getFileContent(const string& path, SmartBuffer& content, time_t& utime)
{
	return cachefile.getContent(path, content, utime);
}

void HttpServer::loop()
{
	Process::SetDaemonCommand("restart");

	stdx::timer(60 * 1000, [](){
		time_t now;
		DateTime tmp(time(&now));

		if (tmp.canUse())
		{
			static DateTime date(now);

			if (date.day == tmp.day)
			{
				app->sessmap.clear([now](sp<HttpSession>& session){
					CHECK_FALSE_RETURN(session->isTimeout(now));

					LogTrace(eINF, "session[%s] timeout", session->getName().c_str());

					return true;
				});
			}
			else
			{
				SpinLocker lk(app->transmtx);
				app->transmap.clear();
				std::swap(date, tmp);
			}
		}

		LogTrace(eTIP, "check session success");
	});

	int backlog = TaskQueue::Instance()->getThreads() << 3;

	if (sslport > 0)
	{
		std::thread([&](){
			Sleep(100);

			ServerSocketLoop(sslhost.c_str(), sslport, backlog, timeout);

			LogTrace(eERR, "listen host[%s][%d] failed[%s]", sslhost.c_str(), sslport, GetErrorString().c_str());
		}).detach();
	}

	ServerSocketLoop(host.c_str(), port, backlog, timeout);

	Process::SetDaemonCommand("exit");

	LogThread::Instance()->setLogFlag(2);

	LogTrace(eERR, "listen host[%s][%d] failed[%s]", host.c_str(), port, GetErrorString().c_str());
}

int HttpServer::ProcessConnect(SOCKET conn, stConnectData* data, const char* host, int port)
{
	static HttpServer* app = Instance();
	static char* cmd = app->getShareData()->data;
	static u_short* socktimeslist = app->socktimeslist;

	socktimeslist[(u_short)(conn)] = 0;
	SocketSetSendTimeout(conn, SOCKECT_SENDTIMEOUT);
	SocketSetRecvTimeout(conn, SOCKECT_RECVTIMEOUT);

	if (*cmd)
	{
		if (Instance()->isActive()) Instance()->command(cmd);

		return XG_NETCLOSE;
	}

	if (app->sslport == port && app->sslhost == host)
	{
		sp<SSLWorkItem> item = newsp<SSLWorkItem>(conn, data->addr);

		if (item->init() < 0) return XG_SYSERR;

		app->sslconnmap.set(conn, item);

		return stdx::async(item) ? XG_DETACHCONN : XG_SYSBUSY;
	}

	LogTrace(eDBG, "connect[%s] success", data->addr);

	return XG_OK;
}

int HttpServer::ReleaseConnect(SOCKET conn, const char* addr)
{
	LogTrace(eDBG, "connect[%s] shutdown", addr);

	app->streamap.remove(conn);
	
	if (app->sslconnmap.remove(conn))
	{
		if (stdx::delay(3000, [conn]{
			SocketClose(conn);
		})) return XG_DETACHCONN;
	}

	SocketClose(conn);

	return XG_DETACHCONN;
}

int HttpServer::ProcessConnectClosed(SOCKET conn, stConnectData* data)
{
	return ReleaseConnect(conn, data->addr);
}

void HttpRequestItem::run()
{
	SOCKET conn = sock->getHandle();

	if (readed >= 0)
	{
		if (ServerSocketAttach(conn, addr.c_str()))
		{
			LogTrace(eDBG, "connect[%s] attach", addr.c_str());
		}
		else
		{
			HttpServer::ReleaseConnect(conn, addr.c_str());
		}
	}
	else
	{
		LogTrace(eERR, "connect[%s] process failed[%d]", addr.c_str(), readed);

		HttpServer::ReleaseConnect(conn, addr.c_str());
	}
}
int HttpRequestItem::process()
{
	int val = XG_SYSERR;
	HttpResponse response;
	const string& url = request.getPath();
	static u_short* socktimeslist = app->socktimeslist;

	if (response.init(&request, sock, addr))
	{
		sp<Response> rspdata = newsp<Response>();
		u_short& num = socktimeslist[(u_short)(sock->getHandle())];

		if (++num >= app->getMaxRequestTimes())
		{
			response.setHeadValue("Connection", "close");

			num = 0;
		}
		else
		{
			response.setHeadValue("Connection", "keep-alive");
			response.setHeadValue("Keep-Alive", "timeout=" + stdx::str(app->getTimeout()));
		}

		val = response.forward(url, addr, [&](const void* msg, int len, const string& addr){
			if ((len = rspdata->init(&response, msg, len)) < 0) return len;

			rsplist.push_back(rspdata);

			return len;
		});

		if (val == XG_NOTFOUND || val == XG_AUTHFAIL)
		{
			if (val == XG_NOTFOUND)
			{
				LogTrace(eIMP, "request[%s][%s] resource not found", addr.c_str(), url.c_str());
			
				val = rspdata->init(response.getNotFoundContent());
			}
			else
			{
				LogTrace(eIMP, "request[%s][%s] resource forbidden", addr.c_str(), url.c_str());
			
				val = rspdata->init(response.getRefusedContent());
			}

			if (val > 0) rsplist.push_back(rspdata);
		}
	}

	return val;
}
bool HttpRequestItem::tryRead()
{
	int res = 0;
	int offset = readed;
	time_t now = time(NULL);
	static HttpServer::ShareData* shd = app->getShareData();

	if (shd->data[0])
	{
		readed = XG_SYSBUSY;

		return true;
	}

	if (utime > 0)
	{
		res = request.init(sock.get(), buffer, readed, true);
	}
	else
	{
		res = request.init(sock.get(), buffer, readed, false);

		utime = now;
	}

	if (res == XG_TIMEOUT)
	{
		if (readed > offset + SOCKET_TIMEOUT_LIMITSIZE)
		{
			utime = now;
		}
		else if (utime + app->getTimeout() < now)
		{
			readed = XG_TIMEOUT;

			return true;
		}

		return false;
	}

	if (res < 0)
	{
		readed = res;

		return true;
	}

	if (readed > offset + SOCKET_TIMEOUT_LIMITSIZE)
	{
		utime = now;
	}
	else if (utime + app->getTimeout() < now)
	{
		readed = XG_TIMEOUT;

		return true;
	}

	int addsz = request.getAdditionSize();

	if (addsz > 0)
	{
		if (maxsz == 0)
		{
			const string& url = request.getPath();

			if (url.empty())
			{
				readed = XG_DATAERR;

				return true;
			}

			maxsz = app->getCgiMapData(url).maxsz;
		}

		if (readed + addsz <= maxsz)
		{
			buffer.truncate(readed + addsz);

			return false;
		}
	}

	return true;
}
bool HttpRequestItem::trySend()
{
	if (rsplist.empty()) return readed <= 0;

	sp<Response>& rsp = rsplist.front();
	int res = rsp->trySend(sock.get());

	if (res < 0)
	{
		readed = res;

		return true;
	}

	CHECK_FALSE_RETURN(rsp->empty());

	rsplist.pop_front();

	return readed <= 0 && rsplist.empty();
}
bool HttpRequestItem::runnable()
{
	if (trySend()) return true;

	CHECK_FALSE_RETURN(readed > 0 && tryRead());

	if (readed < 0) return true;

	int res = process();

	if (res < 0)
	{
		LogTrace(eERR, "request[%s][%s] process failed[%d]", addr.c_str(), request.getPath().c_str(), res);

		return true;
	}

	LogTrace(eINF, "request[%s][%s] process success[%d]", addr.c_str(), request.getPath().c_str(), res);

	int addsz = request.getAdditionSize();

	if (addsz < 0)
	{
		memcpy(buffer.str(), buffer.str() + readed + addsz, -addsz);
		readed = -addsz;
		request.clear();
		utime = 0;

		return false;
	}

	readed = 0;

	return trySend();
}
int HttpRequestItem::Response::trySend(Socket* sock)
{
	int len = data.size();
	int val = sock->write(data.str() + writed, len - writed, false);

	if (val < 0) return val;

	if (val > SOCKET_TIMEOUT_LIMITSIZE)
	{
		utime = time(NULL);
	}
	else
	{
		if (utime + app->getTimeout() < time(NULL)) return XG_TIMEOUT;
	}

	if ((writed += val) >= len)
	{
		if (filelen <= 0) return writed;

		if (len != SOCKECT_FRAMESIZE) data.malloc(len = SOCKECT_FRAMESIZE);

		if ((val = file->read(data.str(), len)) <= 0) return XG_IOERR;

		if ((filelen -= val) < 0) return XG_IOERR;

		if (val < len) data.truncate(val);

		writed = 0;
	}

	return writed;
}
int HttpRequestItem::Response::init(const SmartBuffer& data)
{
	this->data = data;

	return data.size();
}
int HttpRequestItem::Response::init(HttpResponse* response, const void* msg, int len)
{
	if (len < 0)
	{
		file = newsp<XFile>();

		if (file->open((char*)(msg), eREAD)) filelen = file->size();

		if (filelen < 0) return XG_NOTFOUND;

		response->setHeadValue("Content-Length", stdx::str(filelen));

		data = response->getHeadString();

		return filelen;
	}
	else
	{
		response->setHeadValue("Content-Length", stdx::str(len));

		string head = response->getHeadString();
		int hdrsz = head.length();

		memcpy(data.malloc(hdrsz + len), head.c_str(), hdrsz);

		if (len > 0) memcpy(data.str() + hdrsz, msg, len);

		return len;
	}
}

void HttpServer::Lock()
{
	static SpinMutex& mtx = Instance()->svrmtx;

	mtx.lock();
}
void HttpServer::Unlock()
{
	static SpinMutex& mtx = Instance()->svrmtx;

	mtx.unlock();
}
bool HttpServer::IsDynamicLoad()
{
	static const char* cmd = Process::GetCmdParam("-d");

	return cmd ? true : false;
}
HttpServer* HttpServer::Instance()
{
	XG_DEFINE_GLOBAL_VARIABLE(HttpServer)
}
bool HttpServer::SkipLoadWebApp()
{
	return stdx::atoi(proc::env("SKIP_LOAD_WEBAPP").c_str()) > 0;
}
const char* HttpServer::GetSharememName()
{
	static string name = stdx::format("%s:webapp:%s", GetCurrentUser(), Instance()->getClassName());

	return name.c_str();
}
const char* HttpServer::GetSemaphoreName()
{
	static string name = stdx::format("%s:semaphore", GetSharememName());

	return name.c_str();
}
string HttpServer::GetWebAppPath(const char* path, const char* filename)
{
	if (path == NULL || *path == 0) path = "${filename}";

	string res = CgiMapData::GetKey(path);

	if (strstr(path, "$classname") || strstr(path, "$(classname)") || strstr(path, "${classname}"))
	{
		size_t pos = 0;
		string name = path::name(filename);

		if ((pos = name.rfind('.')) != string::npos) name = name.substr(0, pos);
		
		res = stdx::replace(res, "$classname", name);
		res = stdx::replace(res, "$(classname)", name);
		res = stdx::replace(res, "${classname}", name);
	}

	if (strstr(path, "$filename") || strstr(path, "$(filename)") || strstr(path, "${filename}"))
	{
		size_t pos = 0;
		string name = path::name(filename);

		if ((pos = name.rfind('.')) != string::npos) name = name.substr(0, pos);
		
		res = stdx::replace(res, "$filename", name);
		res = stdx::replace(res, "$(filename)", name);
		res = stdx::replace(res, "${filename}", name);
	}

	if (strstr(path, "$package") || strstr(path, "$(package)") || strstr(path, "${package}"))
	{
		size_t pos = 0;
		string path = path::parent(filename);

		res = stdx::replace(res, "$package", path);
		res = stdx::replace(res, "$(package)", path);
		res = stdx::replace(res, "${package}", path);
	}

	return res;
}
int HttpServer::ProcessRequest(SOCKET conn, stConnectData* data)
{
	sp<Socket> sock;
	sp<HttpStream> item;
	const int taglen = 10;
	const char* hdrtag = "PRI * HTTP";
	static HttpServer* app = Instance();

	if (app->streamap.get(conn, item)) return stdx::async(item) ? XG_DETACHCONN : XG_SYSBUSY;

	if (app->sslport > 0)
	{
		sp<SSLWorkItem> item;

		if (app->sslconnmap.get(conn, item))
		{
			if (item->errcode < 0) return stdx::async(item) ? XG_DETACHCONN : XG_SYSBUSY;

			sock = item->dest;
		}
	}

	if (!sock)
	{
		sock = newsp<Socket>();
		sock->init(conn);
		sock->setCloseFlag(false);
	}

	SmartBuffer buffer(HTTP_REQHDR_MAXSIZE);
	int readed = sock->read(buffer.str(), buffer.size(), false);

	if (readed < 0) return readed;

	if (readed < taglen)
	{
		LogTrace(eERR, "connect[%s] transport delay", data->addr);

		return XG_NETDELAY;
	}

	if (memcmp(hdrtag, buffer.str(), taglen) == 0)
	{
		item = newsp<HttpStream>(sock, data->addr);

		app->streamap.set(conn, item);

		memcpy(item->buffer, buffer.str(), item->readed = readed);

		return stdx::async(item) ? XG_DETACHCONN : XG_SYSBUSY;
	}
	else
	{
		sp<HttpRequestItem> item = newsp<HttpRequestItem>(sock, data->addr, buffer, readed);

		return stdx::async(item) ? XG_DETACHCONN : XG_SYSBUSY;
	}

	return XG_SYSERR;
}

////////////////////////////////////////////////////////
#endif